﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Solution.Comm.Extensions;
using Solution.Entity.Base;
using Solution.EntityFrameworkCore;
using Solution.Repository.Interfaces;

namespace Solution.Repository
{
    public abstract class RepositoryBase<TEntity, TPrimaryKey> : IRepositoryBase<TEntity, TPrimaryKey>
        where TEntity : BaseEntity<TPrimaryKey>
    {
        private readonly SolutionDbContext _dbContext;

        protected RepositoryBase(SolutionDbContext generalDbContext)
        {
            _dbContext = generalDbContext;
        }

        public DbContext DbContext => _dbContext;

        public IQueryable<TEntity> GetAll()
        {
            return GetAllIncluding();
        }

        public IQueryable<TEntity> GetAllIncluding(params Expression<Func<TEntity, object>>[] propertySelectors)
        {
            var query = _dbContext.Set<TEntity>().AsQueryable();
            return propertySelectors.IsNullOrEmpty()
                ? query : propertySelectors.Aggregate(query, (current, propertySelector) => current.Include(propertySelector));
        }

        public async Task<List<TEntity>> GetAllListAsync()
        {
            return await GetAll().ToListAsync();
        }

        public async Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return await GetAll().Where(predicate).ToListAsync();
        }

        public TEntity FirstOrDefault(TPrimaryKey id)
        {
            return GetAll().FirstOrDefault(CreateEqualityExpressionForId(id));
        }

        public TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate)
        {
            return GetAll().FirstOrDefault(predicate);
        }

        public Task<TEntity> FirstOrDefaultAsync(TPrimaryKey id)
        {
            return Task.FromResult(FirstOrDefault(id));
        }

        public Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return Task.FromResult(FirstOrDefault(predicate));
        }

        public TEntity Get(TPrimaryKey id)
        {
            var entity = FirstOrDefault(id);
            if (entity == null) {
                // throw new EntityNotFoundExc(typeof(TEntity), id);
            }
            return entity;
        }

        public async Task<TEntity> GetAsync(TPrimaryKey id)
        {
            var entity = await FirstOrDefaultAsync(id);
            if (entity == null) {
                //  throw new EntityNotFoundExc(typeof(TEntity), id);
            }
            return entity;
        }

        public TEntity Insert(TEntity entity)
        {
            entity.AddedTime = DateTime.Now;
            entity.ModifiedTime = DateTime.Now;
            DbContext.Set<TEntity>().Add(entity);

            return entity;
        }

        public Task<TEntity> InsertAsync(TEntity entity)
        {
            return Task.FromResult(Insert(entity));
        }

        public TPrimaryKey InsertAndGetId(TEntity entity)
        {
            return Insert(entity).Id;
        }

        public async Task<TPrimaryKey> InsertAndGetIdAsync(TEntity entity)
        {
            entity = await InsertAsync(entity);
            return entity.Id;
        }

        public TEntity Update(TEntity entity, Action<TEntity> updateAction = null)
        {
            var obj = Get(entity.Id);
            if (updateAction == null) {
                EntityToEntity(entity, obj);
            }
            else {
                updateAction(obj);
            }
            obj.ModifiedTime = DateTime.Now;

            return obj;
        }

        public Task<TEntity> UpdateAsync(TEntity entity, Action<TEntity> updateAction = null)
        {
            return Task.FromResult(Update(entity, updateAction));
        }

        public TEntity InsertOrUpdate(TEntity entity)
        {
            return FirstOrDefault(entity.Id) == null ? Insert(entity) : Update(entity);
        }

        public async Task<TEntity> InsertOrUpdateAsync(TEntity entity)
        {
            return FirstOrDefaultAsync(entity.Id) == null ? await InsertAsync(entity) : await UpdateAsync(entity);
        }

        public void Delete(TEntity entity)
        {
            DbContext.Set<TEntity>().Remove(entity);
        }

        public void Delete(TPrimaryKey id)
        {
            DbContext.Set<TEntity>().Remove(FirstOrDefault(id));
        }

        public void Delete(Expression<Func<TEntity, bool>> predicate)
        {
            GetAll().Where(predicate).ToList().ForEach(Delete);
        }

        public Task DeleteAsync(TEntity entity)
        {
            Delete(entity);
            return Task.FromResult(0);
        }

        public Task DeleteAsync(TPrimaryKey id)
        {
            Delete(id);
            return Task.FromResult(0);
        }

        public Task DeleteAsync(Expression<Func<TEntity, bool>> predicate)
        {
            Delete(predicate);
            return Task.FromResult(0);
        }

        public IQueryable<TEntity> LoadPageList<TSKey>(int startPage, int pageSize, out int rowCount, Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TSKey>> order, bool isAcs = true)
        {
            var result = from p in DbContext.Set<TEntity>()

                         select p;
            rowCount = result.Where(predicate).Count();
            if (isAcs) {
                var temp = result.Where(predicate)
                    .OrderBy(order)
                    .Skip((startPage - 1) * pageSize)
                    .Take(pageSize);
                return temp;
            }
            else {
                var temp = result.Where(predicate)
                    .OrderByDescending(order)
                    .Skip((startPage - 1) * pageSize)
                    .Take(pageSize);
                return temp;
            }
            //var QueryList = DbContext.Set<TEntity>().OrderBy(order);
            //if (predicate != null) {
            //    QueryList = QueryList.Where(predicate);
            //}

            //rowCount = QueryList.Count();
            //return QueryList.Skip(pageSize * (startPage - 1)).Take(pageSize) ?? null;
        }

        private static void EntityToEntity<T>(T pTargetObjSrc, T pTargetObjDest)
        {
            foreach (var mItem in typeof(T).GetProperties()) {
                mItem.SetValue(pTargetObjDest, mItem.GetValue(pTargetObjSrc, new object[] { }), null);
            }
        }

        private static Expression<Func<TEntity, bool>> CreateEqualityExpressionForId(TPrimaryKey id)
        {
            var lambdaParam = Expression.Parameter(typeof(TEntity));
            var lambdaBody = Expression.Equal(Expression.PropertyOrField(lambdaParam, "Id"), Expression.Constant(id, typeof(TPrimaryKey)));
            return Expression.Lambda<Func<TEntity, bool>>(lambdaBody, lambdaParam);
        }

        public List<TEntity> ExecQuery(string sql, params object[] parameters)
        {
            return DbContext.Set<TEntity>().FromSql(sql, parameters).ToList();
        }

        public dynamic ExecQuerydynamic(string sql, params object[] parameters)
        {
            return DbContext.Set<dynamic>().FromSql(sql, parameters);
        }
    }

    public abstract class RepositoryBase<TEntity> : RepositoryBase<TEntity, Guid> where TEntity : BaseEntity
    {
        protected RepositoryBase(SolutionDbContext dbContext) : base(dbContext)
        {
        }
    }
}